﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Web;
using System.IO;
using System.Reflection;
using Lucene.Net.Analysis;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.QueryParsers;
using Lucene.Net.Documents;
using Lucene.Net.Store;
using NLog;
using Vit.Common.Extensions;
using Vit.Common.Helpers;
using Vit.PlugIn.Lucene.Converters;
using Search = Lucene.Net.Search;

namespace Vit.PlugIn.Lucene
{
    public class LuceneService
    {
        internal static LuceneInfo Configuration;
        internal static object Lock = new object();
        private static Logger logger;

        public static void Initialize(string path)
        {
            if (Configuration != null)
                return;
            logger = LogManager.GetCurrentClassLogger(typeof(LuceneService));
            logger.Info(string.Format("preparing initializing lucene by {0}", path));
            XDocument doc = XDocument.Load(path);
            Initialize(doc.Root);
        }

        public static void Initialize(XElement ele)
        {
            if (Configuration != null)
                return;
            lock (Lock)
            {
                Configuration = GetLuceneInfo(ele);
            }
        }

        private static LuceneInfo GetLuceneInfo(XElement ele)
        {
            logger.Info("initializing lucene");
            LuceneInfo lu = new LuceneInfo();
            string path = ele.AttributeValue("directory", "lucene");

            lu.DirectoryPath = GetPath(path);
            if (!System.IO.Directory.Exists(lu.DirectoryPath))
                System.IO.Directory.CreateDirectory(lu.DirectoryPath);

            foreach (XElement se in ele.Elements("segment"))
            {
                SegmentInfo seg = GetSegmentInfo(lu, se);
                lu.Segments.Add(seg);
            }

            logger.Info("initialized lucene successfully.");
            return lu;
        }

        private static SegmentInfo GetSegmentInfo(LuceneInfo lu, XElement ele)
        {
            SegmentInfo seg = new SegmentInfo(lu);

            seg.ObjectType = ReflectionHelper.FindTypeInAppDomain(ele.AttributeValue("type", null));
            seg.Index = GetIndexInfo(seg, ele.Element("index"));
            seg.Search = GetSearchInfo(seg, ele.Element("search"));
            seg.Document = GetDocumentInfo(seg, ele.Element("document"));

            return seg;
        }

        private static IndexInfo GetIndexInfo(SegmentInfo seg, XElement ele)
        {
            IndexInfo ii = new IndexInfo(seg);
            string val = ele.AttributeValue("directory", null);
            if (!string.IsNullOrEmpty(val))
                ii.File = GetPath(val);
            val = ele.AttributeValue("analyzer", null);
            if (!string.IsNullOrEmpty(val))
                ii.Analyzer = ReflectionHelper.CreateInstance(ReflectionHelper.FindTypeInAppDomain(val), null) as Analyzer;
            val = ele.AttributeValue("maxMergeDocs", null);
            if (!string.IsNullOrEmpty(val))
                ii.MaxMergeDocs = Convert.ToInt32(val);
            val = ele.AttributeValue("maxBufferedDocs", null);
            if (!string.IsNullOrEmpty(val))
                ii.MaxBufferedDocs = Convert.ToInt32(val);

            return ii;
        }

        private static SearchInfo GetSearchInfo(SegmentInfo seg, XElement ele)
        {
            SearchInfo si = new SearchInfo(seg);
            string val = ele.AttributeValue("analyzer", null);
            if (!string.IsNullOrEmpty(val))
                si.Analyzer = ReflectionHelper.CreateInstance(ReflectionHelper.FindTypeInAppDomain(val), null) as Analyzer;

            return si;
        }

        private static DocumentInfo GetDocumentInfo(SegmentInfo seg, XElement ele)
        {
            DocumentInfo di = new DocumentInfo(seg);
            string val = ele.AttributeValue("accessor", null);
            if (!string.IsNullOrEmpty(val))
                di.EntityAccessor = ReflectionHelper.CreateInstance(ReflectionHelper.FindTypeInAppDomain(val), null) as IEntityAccessor;

            foreach (XElement fe in ele.Elements("field"))
            {
                di.Fields.Add(GetFieldInfo(di, fe));
            }
            return di;
        }

        private static FieldInfo GetFieldInfo(DocumentInfo di, XElement ele)
        {
            FieldInfo fi = new FieldInfo(di);
            fi.FieldName = ele.AttributeValue("name", null);
            fi.PropertyName = ele.AttributeValue("property", fi.FieldName);
            fi.Index = ele.AttributeValue("index", "TOKENIZED");
            fi.Store = ele.AttributeValue("store", "YES");
            fi.Boost = ele.AttributeValue<float>("boost", 1.0f);

            return fi;
        }

        private static string GetPath(string path)
        {
            if (HttpContext.Current != null)
                path = HttpContext.Current.Server.MapPath(VirtualPathUtility.Combine("~/", path));
            else
                path = Path.Combine(Assembly.GetCallingAssembly().Location, path);
            return path;
        }

        private static SegmentInfo GetSegment<T>()
        {
            return GetSegment(typeof(T));
        }

        private static SegmentInfo GetSegment(Type objectType)
        {
            foreach (SegmentInfo seg in Configuration.Segments)
            {
                if (seg.ObjectType.Equals(objectType))
                    return seg;
            }
            return null;
        }

        public static void UpdateIndex<T>(IEnumerable<T> objects)
        {
            logger.Info(string.Format("updating indexes of {0} at {1}", typeof(T).FullName, DateTime.Now));

            SegmentInfo seg = GetSegment<T>();
            FileInfo file = new FileInfo(seg.Index.File);
            bool create = !file.Exists;
            IndexWriter writer = new IndexWriter(file, seg.Index.Analyzer, create);

            EntityDocumentConverter converter = new EntityDocumentConverter();

            foreach (T obj in objects)
            {
                Document doc = converter.Convert(obj, typeof(Document), seg.Document) as Document;
                writer.AddDocument(doc);
            }

            writer.Optimize();
            writer.Commit();
            writer.Close();

            logger.Info(string.Format("updated indexes of {0} at {1}", typeof(T).FullName, DateTime.Now));
        }

        public static TopDocs Search<T>(string query, string[] searchFields, Search.Filter filter, int topN)
        {
            if (string.IsNullOrEmpty(query))
                return null;

            SegmentInfo seg = GetSegment<T>();
            FileInfo file = new FileInfo(seg.Index.File);

            if (searchFields == null || searchFields.Length <= 0)
            {
                searchFields = seg.Document.Fields.Cast<FieldInfo, string>(o => o.FieldName).ToArray();
            }

            IndexReader reader = IndexReader.Open(file);
            Searcher searcher = new IndexSearcher(reader);
            MultiFieldQueryParser parser = new MultiFieldQueryParser(searchFields, seg.Search.Analyzer);
            Query q = parser.Parse(query);
            TopDocs results = searcher.Search(q, filter, topN);
            return results;
        }
    }
}
